﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.IO;
using Microsoft.WindowsAzure.ServiceRuntime;
using System.Text.RegularExpressions;

namespace Azure.TableQuery.Core
{
    public class StorageClientExtensionQueryCompiler
    {
        private const string UsingFormat = "using {0};\r\n";

        private List<GenericEntity> entities = new List<GenericEntity>();
        private string queryText;

        private static readonly string[] imports = 
        { 
            "System", 
            "System.Collections",
            "System.Collections.Generic", 
            "System.Linq", 
            "System.Text", 
            "System.IO",
            "System.Xml.Serialization",
            "System.Web.UI.WebControls",
            "Microsoft.WindowsAzure",
            "Microsoft.WindowsAzure.StorageClient",
            "System.Data.Services.Common",
            "Azure.TableQuery.Core.Interfaces",
            "Azure.TableQuery.Core",
            "AntsCode.StorageClientExtensions"
        };
       
        private static readonly string[] references = 
        { 
            "System", 
            "System.Web",
            "System.Core", 
            "System.Data",
            "System.Data.Services.Client",
            "System.Xml",
            "System.Xml.Linq",
            "Microsoft.WindowsAzure.StorageClient.dll",
            "Azure.TableQuery.Core.dll",
            "Ionic.Zip.dll",
            "AntsCode.StorageClientExtensions.dll"
        };

        public StorageClientExtensionQueryCompiler(List<GenericEntity> entities, string queryText)
        {
            this.queryText = queryText;
            this.entities = entities;
        }

        //private void ParseStorageEntities(string queryText)
        //{
        //    // has to be a more efficient way to do this ...
        //    Match m = Regex.Match(queryText, @"\sin\s(?<1>(\w+))\s");
        //    while (m.Success)
        //    {
        //        string tableName = m.Groups[1].ToString();

        //        m = m.NextMatch();
        //    }
        //}

        private string GetCodeText()
        {
            StringBuilder nsBuilder = new StringBuilder();
            foreach (string ns in imports)
            {
                nsBuilder.AppendFormat(UsingFormat, ns);
            }

            var entityBuilder = new StorageClientExtensionQueryBuilder();
            entityBuilder.AddQuery(entities, queryText);

            return nsBuilder.ToString() + entityBuilder.ToString();
        }

        private string GetEntityNames()
        {
            string names = entities[0].GetTableName();

            for (int i = 1; i < entities.Count; i++)
            {
                names += entities[i].GetTableName();
            }

            return names;
        }

        public string GetOutputFileName()
        {
            if (RoleEnvironment.IsAvailable)
            {
                return Path.Combine(RoleEnvironment.GetLocalResource("CompilerTempFiles").RootPath,
                    GetEntityNames() + "Query.dll");
            }
            else
            {
                return GetEntityNames() + "Query.dll";
            }
        }

        public CompilerResults Compile()
        {
            var compilerOptions = new Dictionary<string, string>();
            compilerOptions.Add("CompilerVersion", "v4.0");

            CodeDomProvider compiler = new CSharpCodeProvider(compilerOptions);
            CompilerParameters parameters = new CompilerParameters();
            parameters.WarningLevel = 4;
            parameters.TreatWarningsAsErrors = false;
            parameters.GenerateExecutable = false;
            parameters.GenerateInMemory = false;
            parameters.OutputAssembly = GetOutputFileName(); 
            parameters.IncludeDebugInformation = false;
            if (RoleEnvironment.IsAvailable)
            {
                parameters.TempFiles = new TempFileCollection(RoleEnvironment.GetLocalResource("CompilerTempFiles").RootPath, false);
            }
            string baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            
            baseDir = baseDir.Substring(0, baseDir.LastIndexOf(Path.DirectorySeparatorChar));
            baseDir = baseDir.Substring(0, baseDir.LastIndexOf(Path.DirectorySeparatorChar));

            string assemblyName;
            foreach (string assembly in references)
            {
                string suffix = assembly.EndsWith(".exe") ? "" : ".dll";
                string prefix = "";
                assemblyName = assembly;
                if (!assembly.StartsWith("System"))
                {
                    if (assembly.IndexOf(".dll") > -1)
                    {
                        assemblyName = FindAssembly(baseDir, assembly);
                        suffix = "";
                    }
                    parameters.ReferencedAssemblies.Add(prefix + assemblyName + suffix);
                }
                else
                {
                    var assemblyPath = (from assm in AppDomain.CurrentDomain.GetAssemblies()
                                       where assm.FullName.IndexOf(assemblyName + ",") > -1
                                       select assm).FirstOrDefault();
                   
                    if (assemblyPath == null)
                    {
                        throw new Exception("Unable to locate assembly: " + assemblyName + suffix);
                    }

                    parameters.ReferencedAssemblies.Add(assemblyPath.Location);
                }
                
            }
            
            CompilerResults results = compiler.CompileAssemblyFromSource(parameters, GetCodeText());
            
            //OnCompileComplete(results);
            return results;
        }

        private string FindAssembly(string baseDirectory, string assemblyName)
        {
            string[] fileNames = Directory.GetFiles(baseDirectory, assemblyName, SearchOption.AllDirectories);
            return fileNames[0];
        }

        private void OnCompileComplete(CompilerResults results)
        {
            if (!results.Errors.HasErrors)
            {
             
            }
            else
            {
                foreach (CompilerError error in results.Errors)
                {
                
                }
                
            }
        }

    }
}
